<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
    <title>WebGL - 2D image 3x3 convolution</title>
    <link type="text/css" href="../resources/webgl-tutorials.css" rel="stylesheet" />
</head>

<body>
    <div id="info">
        <a href="https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-image-processing.html" target="_blank">WebGL
            图像处理</a>
    </div>
    <canvas id="canvas"></canvas>
    <div id="uiContainer">
        <div id="ui"></div>
    </div>
</body>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform vec2 u_resolution;

varying vec2 v_texCoord;

void main() {
    // 从像素坐标转换到 0.0 到 1.0
    vec2 zeroToOne = a_position / u_resolution;

    // 再把 0->1 转换 0->2
    vec2 zeroToTwo = zeroToOne * 2.0;

    // 把 0->2 转换到 -1->+1 (裁剪空间)
    vec2 clipSpace = zeroToTwo - 1.0;

    // 翻转y轴，使起点在左上角
    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

    // 将纹理坐标传给片断着色器
    // GPU会在点之间进行插值
    v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// 纹理
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;

// 从顶点着色器传入的纹理坐标
varying vec2 v_texCoord;

void main() {
    vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
    vec4 colorSum =
        texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
        texture2D(u_image, v_texCoord + onePixel * vec2(-1,  0)) * u_kernel[3] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0,  0)) * u_kernel[4] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1,  0)) * u_kernel[5] +
        texture2D(u_image, v_texCoord + onePixel * vec2(-1,  1)) * u_kernel[6] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 0,  1)) * u_kernel[7] +
        texture2D(u_image, v_texCoord + onePixel * vec2( 1,  1)) * u_kernel[8] ;

    // 只把rgb值求和除以权重
    // 将阿尔法值设为 1.0
    gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1);
}
</script>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="../resources/webgl-utils.js"></script>
<script>
    "use strict";

    function main() {
        var image = new Image();
        image.src = "../resources/leaves.jpg";  // 必须在同一域名下
        image.onload = function () {
            render(image);
        };
    }

    function render(image) {
        // 获取WebGL上下文
        /** @type {HTMLCanvasElement} */
        var canvas = document.getElementById("canvas");
        var gl = canvas.getContext("webgl");
        if (!gl) {
            return;
        }

        // 创建着色程序
        var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);

        // 从着色程序中找到attribute属性值所在的位置
        var positionLocation = gl.getAttribLocation(program, "a_position");
        var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");

        // 创建存放三个2维裁剪空间点的缓存
        var positionBuffer = gl.createBuffer();
        // 绑定位置信息缓冲（下面的绑定点就是ARRAY_BUFFER）
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        // 设置矩形与图片大小相同
        setRectangle(gl, 0, 0, image.width, image.height);

        // 给矩形提供纹理坐标
        var texcoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 0.0,
            1.0, 0.0,
            0.0, 1.0,
            0.0, 1.0,
            1.0, 0.0,
            1.0, 1.0,
        ]), gl.STATIC_DRAW);

        // 创建纹理
        var texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        // 设置参数，让我们可以绘制任何尺寸的图像
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        // 将图像上传到纹理
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        // 从着色程序中找到uniforms属性值所在的位置 
        var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
        var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
        var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
        var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");

        // 定义多种卷积内核
        var kernels = {
            normal: [
                0, 0, 0,
                0, 1, 0,
                0, 0, 0
            ],
            gaussianBlur: [
                0.045, 0.122, 0.045,
                0.122, 0.332, 0.122,
                0.045, 0.122, 0.045
            ],
            gaussianBlur2: [
                1, 2, 1,
                2, 4, 2,
                1, 2, 1
            ],
            gaussianBlur3: [
                0, 1, 0,
                1, 1, 1,
                0, 1, 0
            ],
            unsharpen: [
                -1, -1, -1,
                -1, 9, -1,
                -1, -1, -1
            ],
            sharpness: [
                0, -1, 0,
                -1, 5, -1,
                0, -1, 0
            ],
            sharpen: [
                -1, -1, -1,
                -1, 16, -1,
                -1, -1, -1
            ],
            edgeDetect: [
                -0.125, -0.125, -0.125,
                -0.125, 1, -0.125,
                -0.125, -0.125, -0.125
            ],
            edgeDetect2: [
                -1, -1, -1,
                -1, 8, -1,
                -1, -1, -1
            ],
            edgeDetect3: [
                -5, 0, 0,
                0, 0, 0,
                0, 0, 5
            ],
            edgeDetect4: [
                -1, -1, -1,
                0, 0, 0,
                1, 1, 1
            ],
            edgeDetect5: [
                -1, -1, -1,
                2, 2, 2,
                -1, -1, -1
            ],
            edgeDetect6: [
                -5, -5, -5,
                -5, 39, -5,
                -5, -5, -5
            ],
            sobelHorizontal: [
                1, 2, 1,
                0, 0, 0,
                -1, -2, -1
            ],
            sobelVertical: [
                1, 0, -1,
                2, 0, -2,
                1, 0, -1
            ],
            previtHorizontal: [
                1, 1, 1,
                0, 0, 0,
                -1, -1, -1
            ],
            previtVertical: [
                1, 0, -1,
                1, 0, -1,
                1, 0, -1
            ],
            boxBlur: [
                0.111, 0.111, 0.111,
                0.111, 0.111, 0.111,
                0.111, 0.111, 0.111
            ],
            triangleBlur: [
                0.0625, 0.125, 0.0625,
                0.125, 0.25, 0.125,
                0.0625, 0.125, 0.0625
            ],
            emboss: [
                -2, -1, 0,
                -1, 1, 1,
                0, 1, 2
            ]
        };
        var initialSelection = 'edgeDetect2';

        // 创建UI界面选择卷积内核
        var ui = document.getElementById("ui");
        var select = document.createElement("select");
        for (var name in kernels) {
            var option = document.createElement("option");
            option.value = name;
            if (name === initialSelection) {
                option.selected = true;
            }
            option.appendChild(document.createTextNode(name));
            select.appendChild(option);
        }
        select.onchange = function (event) {
            drawWithKernel(this.options[this.selectedIndex].value);
        };
        ui.appendChild(select);
        drawWithKernel(initialSelection);

        function computeKernelWeight(kernel) {
            var weight = kernel.reduce(function (prev, curr) {
                return prev + curr;
            });
            return weight <= 0 ? 1 : weight;
        }

        function drawWithKernel(name) {
            webglUtils.resizeCanvasToDisplaySize(gl.canvas);

            // 告诉WebGl怎样把裁剪空间坐标对应到屏幕坐标
            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

            // 清空画布
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);

            // 告诉它用我们之前写好的着色程序（一个着色器对）
            gl.useProgram(program);

            // 启用对应属性
            gl.enableVertexAttribArray(positionLocation);

            // 将绑定点绑定到缓冲数据（positionBuffer）
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            // 告诉属性怎么从positionBuffer中读取数据(ARRAY_BUFFER)
            var size = 2;          // 每次迭代运行提取两个单位数据
            var type = gl.FLOAT;   // 每个单位的数据类型是32位浮点型
            var normalize = false; // 不需要归一化数据
            var stride = 0;        // 0 = 移动单位数量 * 每个单位占用内存（sizeof(type)）每次迭代运行运动多少内存到下一个数据开始点
            var offset = 0;        // 从缓冲起始位置开始读取
            gl.vertexAttribPointer(
                positionLocation, size, type, normalize, stride, offset);

            // 启用对应属性
            gl.enableVertexAttribArray(texcoordLocation);

            // 将绑定点绑定到缓冲数据
            gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

            // 告诉属性怎么从texcoordBuffer中读取数据(ARRAY_BUFFER)
            var size = 2;          // 每次迭代运行提取两个单位数据
            var type = gl.FLOAT;   // 每个单位的数据类型是32位浮点型
            var normalize = false; // 不需要归一化数据
            var stride = 0;        // 0 = 移动单位数量 * 每个单位占用内存（sizeof(type)）每次迭代运行运动多少内存到下一个数据开始点
            var offset = 0;        // 从缓冲起始位置开始读取
            gl.vertexAttribPointer(
                texcoordLocation, size, type, normalize, stride, offset);

            // 设置全局变量 分辨率
            gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

            // 设置图像的大小
            gl.uniform2f(textureSizeLocation, image.width, image.height);

            // 设置卷积内核及其值
            gl.uniform1fv(kernelLocation, kernels[name]);
            gl.uniform1f(kernelWeightLocation, computeKernelWeight(kernels[name]));

            // 绘制矩形
            var primitiveType = gl.TRIANGLES;
            var offset = 0;
            var count = 6;
            gl.drawArrays(primitiveType, offset, count);
        }
    }

    function setRectangle(gl, x, y, width, height) {
        var x1 = x;
        var x2 = x + width;
        var y1 = y;
        var y2 = y + height;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            x1, y1,
            x2, y1,
            x1, y2,
            x1, y2,
            x2, y1,
            x2, y2,
        ]), gl.STATIC_DRAW);
    }

    main();
</script>

</html>